<?php
namespace Yjius\common;

// +----------------------------------------------------------------------
// | 经纬度转换类，BD-09(百度GPS标准)、GCJ-02(中国国家测绘局标准)、WGS-84(全球GPS标准)相互转换
// +----------------------------------------------------------------------

/**
 * 经纬度转换类，BD-09(百度GPS标准)、GCJ-02(中国国家测绘局标准)、WGS-84(全球GPS标准)相互转换
 * WGS-84：是国际标准，GPS坐标（Google Earth使用、或者GPS模块）
 * GCJ-02：中国坐标偏移标准，Google Map、高德、腾讯使用
 * BD-09：百度坐标偏移标准，Baidu Map使用
 * //两个百度坐标间的距离
 * GPS::bd_distance();
 *
 * //百度坐标转国际gps
 * GPS::bd2gps();
 *
 * //WGS-84 to GCJ-02
 * GPS::gcj_encrypt();
 * //GCJ-02 to WGS-84 粗略
 * GPS::gcj_decrypt();
 * //GCJ-02 to WGS-84 精确(二分极限法)
 * // var threshold = 0.000000001; 目前设置的是精确到小数点后9位，这个值越小，越精确，但是javascript中，浮点运算本身就不太精确，九位在GPS里也偏差不大了
 * GSP.gcj_decrypt_exact();
 * //GCJ-02 to BD-09
 * GPS::bd_encrypt();
 * //BD-09 to GCJ-02
 * GPS::bd_decrypt();
 * //求距离
 * GPS::distance();
 *
 */
class GPSHelper
{
    const PI = 3.14159265358979324;
    const XPI = 3.14159265358979324 * 3000.0 / 180.0;


    //WGS-84 to GCJ-02
    public static function gcj_encrypt($wgsLat, $wgsLon)
    {
        if (self::outOfChina($wgsLat, $wgsLon))
            return array('lat' => $wgsLat, 'lon' => $wgsLon);

        $d = self::delta($wgsLat, $wgsLon);
        return array('lat' => $wgsLat + $d['lat'], 'lon' => $wgsLon + $d['lon']);
    }

    //GCJ-02 to WGS-84
    public static function gcj_decrypt($gcjLat, $gcjLon)
    {
        if (self::outOfChina($gcjLat, $gcjLon))
            return array('lat' => $gcjLat, 'lon' => $gcjLon);

        $d = self::delta($gcjLat, $gcjLon);
        return array('lat' => $gcjLat - $d['lat'], 'lon' => $gcjLon - $d['lon']);
    }

    //GCJ-02 to WGS-84 exactly
    public static function gcj_decrypt_exact($gcjLat, $gcjLon)
    {
        $initDelta = 0.01;
        $threshold = 0.000000001;
        $dLat = $initDelta;
        $dLon = $initDelta;
        $mLat = $gcjLat - $dLat;
        $mLon = $gcjLon - $dLon;
        $pLat = $gcjLat + $dLat;
        $pLon = $gcjLon + $dLon;
        $wgsLat = 0;
        $wgsLon = 0;
        $i = 0;
        while (TRUE) {
            $wgsLat = ($mLat + $pLat) / 2;
            $wgsLon = ($mLon + $pLon) / 2;
            $tmp = self::gcj_encrypt($wgsLat, $wgsLon);
            $dLat = $tmp['lat'] - $gcjLat;
            $dLon = $tmp['lon'] - $gcjLon;
            if ((abs($dLat) < $threshold) && (abs($dLon) < $threshold))
                break;

            if ($dLat > 0) $pLat = $wgsLat; else $mLat = $wgsLat;
            if ($dLon > 0) $pLon = $wgsLon; else $mLon = $wgsLon;

            if (++$i > 10000) break;
        }
        //console.log(i);
        return array('lat' => $wgsLat, 'lon' => $wgsLon);
    }

    //GCJ-02 to BD-09
    public static function bd_encrypt($gcjLat, $gcjLon)
    {
        //如果参数为空不处理
        if(empty($gcjLat) || empty($gcjLon)){
            return array('lat' => $gcjLat, 'lon' => $gcjLon);
        }
        $x = $gcjLon;
        $y = $gcjLat;
        $z = sqrt($x * $x + $y * $y) + 0.00002 * sin($y * self::XPI);
        $theta = atan2($y, $x) + 0.000003 * cos($x * self::XPI);
        $bdLon = $z * cos($theta) + 0.0065;
        $bdLat = $z * sin($theta) + 0.006;
        return array('lat' => $bdLat, 'lon' => $bdLon);
    }

    //BD-09 to GCJ-02
    public static function bd_decrypt($bdLat, $bdLon)
    {
        //如果参数为空不处理
        if(empty($bdLat) || empty($bdLon)){
            return array('lat' => $bdLat, 'lon' => $bdLon);
        }
        $x = $bdLon - 0.0065;
        $y = $bdLat - 0.006;
        $z = sqrt($x * $x + $y * $y) - 0.00002 * sin($y * self::XPI);
        $theta = atan2($y, $x) - 0.000003 * cos($x * self::XPI);
        $gcjLon = $z * cos($theta);
        $gcjLat = $z * sin($theta);
        return array('lat' => $gcjLat, 'lon' => $gcjLon);
    }



    /**
     * 百度坐标转国际gps经纬度
     * @param float $bdLat
     * @param float $bdLon
     * @return $array
     * 返回格式
     * $arr{
     *    ['lat'] = 国际gps纬度
     *    ['lon'] = 国际gps精度
     * }
     */
    public static function bd2gps($bdLat, $bdLon)
    {
        $gcj02 = self::bd_decrypt($bdLat, $bdLon);
        return self::gcj_decrypt($gcj02['lat'], $gcj02['lon']);
    }

    //WGS-84 to Web mercator
    //$mercatorLat -> y $mercatorLon -> x
    public static function mercator_encrypt($wgsLat, $wgsLon)
    {
        $x = $wgsLon * 20037508.34 / 180.;
        $y = log(tan((90. + $wgsLat) * self::PI / 360.)) / (self::PI / 180.);
        $y = $y * 20037508.34 / 180.;
        return array('lat' => $y, 'lon' => $x);
    }
    // Web mercator to WGS-84
    // $mercatorLat -> y $mercatorLon -> x
    public static function mercator_decrypt($mercatorLat, $mercatorLon)
    {
        $x = $mercatorLon / 20037508.34 * 180.;
        $y = $mercatorLat / 20037508.34 * 180.;
        $y = 180 / self::PI * (2 * atan(exp($y * self::PI / 180.)) - self::PI / 2);
        return array('lat' => $y, 'lon' => $x);
    }

    // two point's distance
    public static function distance($latA, $lonA, $latB, $lonB)
    {
        $earthR = 6371393.;
        $x = cos($latA * self::PI / 180.) * cos($latB * self::PI / 180.) * cos(($lonA - $lonB) * self::PI / 180);
        $y = sin($latA * self::PI / 180.) * sin($latB * self::PI / 180.);
        $s = $x + $y;
        if ($s > 1) $s = 1;
        if ($s < -1) $s = -1;
        $alpha = acos($s);
        $distance = $alpha * $earthR;
        return $distance;
    }

    public static function bd_distance($latA, $lonA, $latB, $lonB)
    {
        $gps1 = self::bd2gps($latA, $lonA);
        $gps2 = self::bd2gps($latB, $lonB);
        return self::distance($gps1['lat'], $gps1['lon'], $gps2['lat'], $gps2['lon']);
    }

    private static function delta($lat, $lon)
    {
        $a = 6378245.0;//  a: 卫星椭球坐标投影到平面地图坐标系的投影因子。
        $ee = 0.00669342162296594323;//  ee: 椭球的偏心率。
        $dLat = self::transformLat($lon - 105.0, $lat - 35.0);
        $dLon = self::transformLon($lon - 105.0, $lat - 35.0);
        $radLat = $lat / 180.0 * self::PI;
        $magic = sin($radLat);
        $magic = 1 - $ee * $magic * $magic;
        $sqrtMagic = sqrt($magic);
        $dLat = ($dLat * 180.0) / (($a * (1 - $ee)) / ($magic * $sqrtMagic) * self::PI);
        $dLon = ($dLon * 180.0) / ($a / $sqrtMagic * cos($radLat) * self::PI);
        return array('lat' => $dLat, 'lon' => $dLon);
    }

    private static function outOfChina($lat, $lon)
    {
        if ($lon < 72.004 || $lon > 137.8347)
            return TRUE;
        if ($lat < 0.8293 || $lat > 55.8271)
            return TRUE;
        return FALSE;
    }

    private static function transformLat($x, $y)
    {
        $ret = -100.0 + 2.0 * $x + 3.0 * $y + 0.2 * $y * $y + 0.1 * $x * $y + 0.2 * sqrt(abs($x));
        $ret += (20.0 * sin(6.0 * $x * self::PI) + 20.0 * sin(2.0 * $x * self::PI)) * 2.0 / 3.0;
        $ret += (20.0 * sin($y * self::PI) + 40.0 * sin($y / 3.0 * self::PI)) * 2.0 / 3.0;
        $ret += (160.0 * sin($y / 12.0 * self::PI) + 320 * sin($y * self::PI / 30.0)) * 2.0 / 3.0;
        return $ret;
    }

    private static function transformLon($x, $y)
    {
        $ret = 300.0 + $x + 2.0 * $y + 0.1 * $x * $x + 0.1 * $x * $y + 0.1 * sqrt(abs($x));
        $ret += (20.0 * sin(6.0 * $x * self::PI) + 20.0 * sin(2.0 * $x * self::PI)) * 2.0 / 3.0;
        $ret += (20.0 * sin($x * self::PI) + 40.0 * sin($x / 3.0 * self::PI)) * 2.0 / 3.0;
        $ret += (150.0 * sin($x / 12.0 * self::PI) + 300.0 * sin($x / 30.0 * self::PI)) * 2.0 / 3.0;
        return $ret;
    }

    /*
     * 根据经纬度获取两地的距离
     *  lng 经度 , lat 纬度
     */
    public static function getDistanceByLonAndLat($lng1, $lat1, $lng2, $lat2)
    {
        if (empty($lng1) || empty($lat1) || empty($lng2) || empty($lat2)) {
            return 0;
        }

        if (!is_numeric($lng1) || !is_numeric($lat1) || !is_numeric($lng2) || !is_numeric($lat2)) {
            return 0;
        }
        //将角度转为狐度
        $radLat1 = deg2rad((float)$lat1);//deg2rad()函数将角度转换为弧度
        $radLat2 = deg2rad((float)$lat2);
        $radLng1 = deg2rad((float)$lng1);
        $radLng2 = deg2rad((float)$lng2);
        $a = $radLat1 - $radLat2;
        $b = $radLng1 - $radLng2;
        $s = 2 * asin(sqrt(pow(sin($a / 2), 2) + cos($radLat1) * cos($radLat2) * pow(sin($b / 2), 2))) * 6378.137 * 1000;
        return $s;
    }
}

?>